/*
 * Copyright (C) 2025 ttwe77
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.Arrays;
import java.util.UUID;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.IIOImage;
import javax.imageio.ImageWriteParam;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.ImageTypeSpecifier;
import org.w3c.dom.Node;
import javax.imageio.metadata.IIOMetadataNode;

/**
 * TripleDoublePinyinKeygen_HiRes_UUID - 高分辨率版（每个键盘下显示相同 UUID，全部大写）
 *
 * Usage:
 * javac TripleDoublePinyinKeygen_HiRes_UUID.java
 * java TripleDoublePinyinKeygen_HiRes_UUID [输出文件名.png] [scale] [dpi]
 *
 * scale: 图像放大倍数（默认 3）
 * dpi: 输出 PNG DPI（默认 300）
 */
public class TripleDoublePinyinKeygen_HiRes_UUID {

    // QWERTY 键位顺序（26 个键）
    private static final char[] QWERTY = {
            'Q','W','E','R','T','Y','U','I','O','P',
            'A','S','D','F','G','H','J','K','L',
            'Z','X','C','V','B','N','M'
    };

    // 小鹤双拼：严格按照你给的对应（Q 到 M）
    private static final String[] XIAOHE = new String[] {
            "iu",        // Q
            "ei",        // W
            "e",         // E
            "uan&üan",  // R
            "ue&üe",    // T
            "un&ün",    // Y
            "u",         // U
            "i",         // I
            "uo&o",     // O
            "ie",        // P
            "a",         // A
            "ong&iong", // S
            "ai",        // D
            "en",        // F
            "eng",       // G
            "ang",       // H
            "an",        // J
            "uai&ing",  // K
            "uang&iang",// L
            "ou",        // Z
            "ua&ia",    // X
            "ao",        // C
            "ui&ü",     // V
            "in",        // B
            "iao",       // N
            "ian"        // M
    };

    public static void main(String[] args) throws Exception {
		// 生成一个 UUID（全部大写），在两个键盘下保持一致
        String uuid = UUID.randomUUID().toString().toUpperCase();
		String filename = uuid+".png";
        String outFile = args.length > 0 ? args[0] : filename;
        int scale = args.length > 1 ? Integer.parseInt(args[1]) : 4; // 放大倍数（像素密度）
        int dpi = args.length > 2 ? Integer.parseInt(args[2]) : 600; // 输出 DPI

        // 原始逻辑尺寸（易于调整）——将会乘以 scale
        int baseWidth = 900;
        int baseHeight = 750; // 给上下两个键盘留出空间
        int width = baseWidth * scale;
        int height = baseHeight * scale;

        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = img.createGraphics();

        // 更全面的高质量渲染设置（避免模糊）
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);

        // 背景
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, height);

        SecureRandom rnd = new SecureRandom();

        // 随机分配表1（A..Z -> a..z，不重复）
        char[] lowerAlpha = new char[26];
        for (int i = 0; i < 26; i++) lowerAlpha[i] = (char) ('a' + i);
        shuffleArray(lowerAlpha, rnd);

        // upper A..Z
        char[] upperAlpha = new char[26];
        for (int i = 0; i < 26; i++) upperAlpha[i] = (char) ('A' + i);

        // p 映射（表1）: Upper -> lower
        Map<Character, Character> p = new HashMap<>();
        for (int i = 0; i < 26; i++) p.put(upperAlpha[i], lowerAlpha[i]);

        // inv 映射（表2）构造：保证如果表1中 A 的右上是 g，则表2 中 G 的右上是 a
        Map<Character, Character> inv = new HashMap<>();
        for (int i = 0; i < 26; i++) {
            char U = upperAlpha[i];
            char uLower = Character.toLowerCase(U);
            int pos = -1;
            for (int j = 0; j < 26; j++) if (lowerAlpha[j] == uLower) { pos = j; break; }
            if (pos == -1) inv.put(U, '?');
            else inv.put(U, Character.toLowerCase(upperAlpha[pos]));
        }

        // 音调 1..4，按键位（qwerty 索引）随机生成并在两个键盘保持一致
        int[] tones = new int[26];
        for (int i = 0; i < 26; i++) tones[i] = 1 + rnd.nextInt(4);

        // 双拼列表：**严格按 XIAOHE 对应 QWERTY**（不打散）
        List<String> doublePinyin = new ArrayList<>(Arrays.asList(XIAOHE));

        // 布局参数（单个键盘） — 基础尺寸 * scale
        int keyW = 68 * scale;
        int keyH = 68 * scale;
        int hGap = 8 * scale;
        int vGap = 26 * scale;
        int marginX = 60 * scale;
        int startX = marginX;

        // 字体设置（基于 scale 放大）
        Font upperFont = new Font("Consolas", Font.BOLD, 40 * scale);
        Font smallUR = new Font("Consolas", Font.PLAIN, 14 * scale);
        Font smallLR = new Font("Consolas", Font.PLAIN, 16 * scale);
        Font pinyinFont = new Font("Consolas", Font.PLAIN, 14 * scale);
        Font titleFont = new Font("SansSerif", Font.PLAIN, 18 * scale);
        Font uuidFont = new Font("SansSerif", Font.BOLD, 16 * scale);

        // 标题
        g.setColor(Color.BLACK);
        g.setFont(titleFont);
        g.drawString("三重双拼汉字加密方案 对称密钥（上表：映射 p；下表：逆映射 p^{-1}）", startX, 26 * scale);

        // 先绘制上表（表1），位于 topStartY
        int topStartY = 50 * scale;
        drawKeyboardGeneric(g, startX, topStartY, keyW, keyH, hGap, vGap,
                QWERTY, upperFont, smallUR, smallLR, pinyinFont,
                (upperChar, posIndex) -> p.getOrDefault(upperChar, '?'),
                (upperChar, posIndex) -> tones[posIndex],
                doublePinyin);

        // 计算键盘实际占宽（以第一行为准）：10 keys + 9 gaps
        int keyboardWidth = keyW * 10 + hGap * 9;
        // top UUID 位置：在上键盘下方居中显示
        int topUuidY = topStartY + (keyH * 3 + vGap * 3) + (12 * scale);
        drawCenteredString(g, uuid, startX, topUuidY, keyboardWidth, uuidFont);

        // 绘制下表（表2），位于下方（加上一个间隔）
        int interKeyboardGap = 75 * scale;
        int bottomStartY = topStartY + (keyH * 3 + vGap * 2) + interKeyboardGap + (16 * scale); // 留出上面 UUID 区域
        drawKeyboardGeneric(g, startX, bottomStartY, keyW, keyH, hGap, vGap,
                QWERTY, upperFont, smallUR, smallLR, pinyinFont,
                (upperChar, posIndex) -> inv.getOrDefault(upperChar, '?'),
                (upperChar, posIndex) -> tones[posIndex],
                doublePinyin);

        // bottom UUID 位置：在下键盘下方居中显示（同一个 UUID）
        int bottomUuidY = bottomStartY + (keyH * 3 + vGap * 3) + (12 * scale);
        drawCenteredString(g, uuid, startX, bottomUuidY, keyboardWidth, uuidFont);

        // 说明文字（缩放）
        g.setFont(new Font("SansSerif", Font.PLAIN, 12 * scale));
        g.drawString("说明：上表（p）为随机映射；下表为 p 的逆映射（例如：若上表 A 的右上是 g，则下表 G 的右上为 a）。双拼按小鹤双拼键位固定分布（上下表一致）。", startX, height - 20 * scale);

        g.dispose();

        // 将带有 DPI 元数据的 PNG 写出
        writePngWithDPI(img, new File(outFile), dpi);

        System.out.println("生成完成：" + outFile + " (scale=" + scale + ", dpi=" + dpi + ")");
        System.out.println("UUID: " + uuid);
    }

    // 通用绘制单个键盘（按 QWERTY 排列 3 行）
    private static void drawKeyboardGeneric(Graphics2D g, int startX, int startY,
                                            int keyW, int keyH, int hGap, int vGap,
                                            char[] qwerty, Font upperFont, Font smallUR, Font smallLR, Font pinyinFont,
                                            CharProvider topRightProvider, IntProvider toneProvider,
                                            List<String> pinyinList) {
        g.setColor(Color.DARK_GRAY);

        int index = 0;
        int y = startY;
        int x = startX;
        // 第一行 10 键
        for (int col = 0; col < 10; col++) {
            char U = qwerty[index];
            char tr = topRightProvider.get(U, index);
            int t = toneProvider.get(U, index);
            drawKey(g, x, y, keyW, keyH, U, tr, t, pinyinList.get(index), upperFont, smallUR, smallLR, pinyinFont);
            index++; x += keyW + hGap;
        }
        // 第二行（缩进 half key）
        y += keyH + vGap;
        x = startX + (keyW / 2);
        for (int col = 0; col < 9; col++) {
            char U = qwerty[index];
            char tr = topRightProvider.get(U, index);
            int t = toneProvider.get(U, index);
            drawKey(g, x, y, keyW, keyH, U, tr, t, pinyinList.get(index), upperFont, smallUR, smallLR, pinyinFont);
            index++; x += keyW + hGap;
        }
        // 第三行（更大缩进）
        y += keyH + vGap;
        x = startX + keyW;
        for (int col = 0; col < 7; col++) {
            char U = qwerty[index];
            char tr = topRightProvider.get(U, index);
            int t = toneProvider.get(U, index);
            drawKey(g, x, y, keyW, keyH, U, tr, t, pinyinList.get(index), upperFont, smallUR, smallLR, pinyinFont);
            index++; x += keyW + hGap;
        }
    }

    private static void drawKey(Graphics2D g, int x, int y, int w, int h,
                                char U, char topRightChar, int tone, String pinyin,
                                Font upperFont, Font smallUR, Font smallLR, Font pinyinFont) {
        // 背景与边框
        g.setColor(new Color(245,245,245));
        g.fillRoundRect(x, y, w, h, Math.max(8, w/16), Math.max(8, h/16));
        g.setColor(Color.GRAY);
        g.drawRoundRect(x, y, w, h, Math.max(8, w/16), Math.max(8, h/16));

        // 大写字母居中
        g.setFont(upperFont);
        FontMetrics fm = g.getFontMetrics();
        Rectangle2D ub = fm.getStringBounds(String.valueOf(U), g);
        int ux = x + (w - (int)ub.getWidth()) / 2;
        int uy = y + (h / 2) + fm.getAscent() / 2 - (int)(0.15 * fm.getAscent());
        g.setColor(Color.BLACK);
        g.drawString(String.valueOf(U), ux, uy);

        // 右上小写字母（声母）
        if (topRightChar == 0) topRightChar = '?';
        g.setFont(smallUR);
        String tur = String.valueOf(topRightChar);
        FontMetrics fmUR = g.getFontMetrics();
        int urX = x + w - fmUR.stringWidth(tur) - (6 * (w/68));
        int urY = y + fmUR.getAscent() + (6 * (h/68));
        g.drawString(tur, urX, urY);

        // 右下音调数字（对齐键底部）
        g.setFont(smallLR);
        String toneS = String.valueOf(tone);
        FontMetrics fmLR = g.getFontMetrics();
        int lrX = x + w - fmLR.stringWidth(toneS) - (6 * (w/68));
        int lrY = y + h - (6 * (h/68));
        g.drawString(toneS, lrX, lrY);

        // 下方双拼（最多两行）
        g.setFont(pinyinFont);
        FontMetrics fmp = g.getFontMetrics();
        String[] parts = pinyin.split("\n");
        int lines = Math.min(2, parts.length);
        for (int i = 0; i < lines; i++) {
            String line = parts[i];
            int px = x + (w - fmp.stringWidth(line)) / 2;
            int py = y + h + (16 * (h/68)) + i * (fmp.getHeight());
            g.drawString(line, px, py);
        }
    }

    // 居中绘制字符串（在给定的矩形区域内水平居中）
    private static void drawCenteredString(Graphics2D g, String text, int rectX, int rectY, int rectWidth, Font font) {
        g.setFont(font);
        FontMetrics fm = g.getFontMetrics();
        int textWidth = fm.stringWidth(text);
        int x = rectX + (rectWidth - textWidth) / 2;
        int y = rectY + fm.getAscent(); // rectY 给的是 baseline 上方的偏移量，这里直接用 baseline 风格
        g.setColor(Color.DARK_GRAY);
        g.drawString(text, x, y);
    }

    private static void shuffleArray(char[] arr, SecureRandom rnd) {
        for (int i = arr.length - 1; i > 0; i--) {
            int j = rnd.nextInt(i + 1);
            char t = arr[i]; arr[i] = arr[j]; arr[j] = t;
        }
    }

    // provider 接口
    private interface CharProvider {
        char get(char upperChar, int posIndex);
    }

    private interface IntProvider {
        int get(char upperChar, int posIndex);
    }

    // 写出 PNG 并设置 pHYs (pixels per unit — 用于指定 DPI)
    private static void writePngWithDPI(BufferedImage image, File outFile, int dpi) throws Exception {
        // 计算 pixels per meter (pHYs 单位是每米像素数)
        int pixelsPerMeter = (int)Math.round(dpi * 39.3700787); // dpi / 0.0254

        ImageWriter writer = ImageIO.getImageWritersByFormatName("png").next();
        ImageWriteParam writeParam = writer.getDefaultWriteParam();
        ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(image.getType());
        IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);

        // 构造 PNG 元数据节点树
        String nativeFormat = "javax_imageio_png_1.0";
        IIOMetadataNode root = new IIOMetadataNode(nativeFormat);

        IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs");
        pHYs_node.setAttribute("pixelsPerUnitXAxis", Integer.toString(pixelsPerMeter));
        pHYs_node.setAttribute("pixelsPerUnitYAxis", Integer.toString(pixelsPerMeter));
        pHYs_node.setAttribute("unitSpecifier", "meter");
        root.appendChild(pHYs_node);

        try {
            metadata.mergeTree(nativeFormat, root);
        } catch (Exception e) {
            // 如果合并失败，不要中断写入；仍然尝试直接写文件
            System.err.println("警告：无法写入 PNG DPI 元数据 - " + e.getMessage());
        }

        try (ImageOutputStream stream = ImageIO.createImageOutputStream(outFile)) {
            writer.setOutput(stream);
            IIOImage iioImage = new IIOImage(image, null, metadata);
            writer.write(null, iioImage, writeParam);
            writer.dispose();
        }
    }
}
